import socket
import time
from prg.proc.local_link_thread import LocalLink
from prg.proc.udp_command import UdpCommand
from prg.proc.udp_link_receive import UdpReceive
from prg.common.bytes_array import BytesArray
from prg.proc.confirm_thread import UdpConfirm
from prg.proc.udp_receive_bytes import UdpReceiveBytes
from prg.common.object_message import MessageBase
from prg.udp_command_info import UdpCommandInfo
from prg.bll.config_bll import ConfigBll
from prg.common.import_file import *


class UdpLink(MessageBase):
    def __init__(self, addr, link_port, link_type=1):
        super().__init__(name="udp_link")
        self.bind = ConfigBll.instance().get_bind()
        self.connect = ConfigBll.instance().get_connect()
        self.encryption = None
        self.link_port = link_port
        self.link_type = link_type
        self.addr = addr
        self.udp_addr = None
        self.udp_recv_array = BytesArray()
        # self.encryption_code = CommonBase.random_code(10)
        self.task_id = None
        self.closed = True

    def set_task_id(self, task_id):
        self.task_id = task_id

    def run_udp(self, udp_addr):
        self.udp_addr = udp_addr
        self.start()

    def send_close(self, udp):
        print("send close command: %s" % str(self.udp_addr))
        udp_cmd = UdpCommandEntity()
        udp_cmd.command = "close"
        udp_info = UdpCommandInfo(name=0)
        udp_info.set_bytes(udp_cmd, encryption=self.encryption)
        try:
            udp_info.send(socket_item=udp)
        except Exception as e:
            print("send close error: %s" % e)
        finally:
            self.closed = True

    def create_link(self, udp):
        udp_command = UdpCommand(udp)
        recv_thr = UdpReceiveBytes(addr=self.addr)
        if 1 == self.link_type:
            assert self.link_port in self.bind, "link port not find"
            bind_inf = self.bind[self.link_port]
            local_addr = (bind_inf.ip, bind_inf.listen)
            local_link = LocalLink(link_type=self.link_type, local_addr=local_addr)
            local_link.create_listen(command=udp_command)
            recv_thr.add_local_link(link_item=local_link)
            task_id, task_item = PublicEntity.instance().get_task(self.task_id)
            self.encryption = task_item.link.encryption
        else:
            assert self.link_port in self.connect, "link port not find"
            connect_inf = self.connect[self.link_port]
            local_addr = (connect_inf.ip, connect_inf.port)
            local_link = LocalLink(link_type=self.link_type, local_addr=local_addr)
            local_link.create_listen(command=udp_command)
            recv_thr.add_local_link(link_item=local_link)
            self.encryption = ConfigBll.instance().get_encryption().key
        return udp_command, recv_thr

    def entry(self):
        self.closed = False
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        udp_socket.bind(self.udp_addr)
        udp_socket.connect(self.addr)
        time.sleep(5)
        with udp_socket as udp:
            try:
                udp_recv = UdpReceive(socket_item=udp, bytes_array=self.udp_recv_array)
                udp_conf = UdpConfirm(socket_item=udp, array_byte=self.udp_recv_array)
                udp_recv.run_receive(addr=self.addr)
                udp_conf.run_confirm(self.addr)
                print("udp confirm: %s, bind: %s" % (str(self.addr), str(self.udp_addr)))
                udp_command, recv_thr = self.create_link(udp=udp)
                recv_thr.run_recv(self.udp_recv_array, task_id=self.task_id, encryption=self.encryption)
                udp_command.command_run(encryption=self.encryption)
                print("udp connect: %s, bind: %s" % (str(self.addr), str(self.udp_addr)))
                while self.is_run():
                    pass
            except Exception as e:
                print("udp connect error: %s" % e)
            finally:
                self.send_close(udp)
        print("udp connect out: %s, bind: %s" % (str(self.addr), str(self.udp_addr)))



